﻿using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using PokemonGo.RocketAPI.Enums;
using PokemonGo.RocketAPI.Exceptions;
using PokemonGo.RocketAPI.Extensions;
using PokemonGo.RocketAPI;
using System.Xml;
using System.Net;
using System.IO;
using System.Threading;
using Newtonsoft.Json;
using System.Runtime.InteropServices;
using POGOProtos.Data;
using POGOProtos.Map.Fort;
using POGOProtos.Networking.Responses;
using POGOProtos.Enums;
using POGOProtos.Data.Player;
using POGOProtos.Inventory.Item;
using System.Collections.ObjectModel;

namespace PokemonGo.UI.Common
{
    public partial class PokeClient : ViewModelBase
    {

        private Settings _settings = null;
        public Settings ClientSettings
        {
            get { return _settings; }
            set { Set(() => ClientSettings, ref _settings, value); }
        }

        private Client client = null;
        public Client Client
        {
            get { return client; }
            set { Set(() => Client, ref client, value); }
        }
        public IApiFailureStrategy Strategy { get; set; }
        private int Currentlevel = -1;
        private int TotalExperience = 0;
        private int TotalPokemon = 0;
        private DateTime TimeStarted = DateTime.Now;
        public DateTime InitSessionDateTime = DateTime.Now;

        //public Action<Color, string> AppendMsg = null;
        public Action<Color, string> AppendMsg;
        //Task 取消令牌
        public Thread MainThread;
        public Task task;

        public PokeClient(Settings _set)
        {
            ClientSettings = _set;
        }

        public void ColoredConsoleWrite(Color color, string text)
        {
            if (AppendMsg == null)
                return;
            string textToAppend = $"[{DateTime.Now.ToString("HH:mm:ss")}] ({ClientSettings.PtcUsername}): {text}";
            AppendMsg?.Invoke(color, textToAppend);
        }
        public void Start(int delay = 0)
        {
            if (IsRunning)
                return;
            Thread.Sleep(delay * 1000);
            IsRunning = true;
            //task = Task.Factory.StartNew(Execute);
            MainThread = new Thread(Execute);
            MainThread.IsBackground = true;
            MainThread.Start();
        }
        public void Stop()
        {
            IsRunning = false;

            try
            {
                MainThread.Abort();
            }
            catch (Exception)
            {
            }
        }
        public void Execute()
        {
            Client = new Client(ClientSettings, new ApiFailureStrategy());
            CancellationTokenSource cancel = new CancellationTokenSource();
            while (IsRunning)
            {
                try
                {
                    cancel = new CancellationTokenSource();
                    ActionNow = "提交登录";
                    switch (ClientSettings.AuthType)
                    {
                        case AuthType.Ptc:
                            ColoredConsoleWrite(Colors.Green, "正在使用任天堂帐号登录游戏..");
                            //Client.DoPtcLogin(ClientSettings.PtcUsername, ClientSettings.PtcPassword).Wait();
                            Client.Login.DoLogin().Wait();
                            break;
                        case AuthType.Google:
                            ColoredConsoleWrite(Colors.Green, "正在使用谷歌帐号登录游戏..");
                            if (ClientSettings.GoogleRefreshToken == "")
                                ColoredConsoleWrite(Colors.Green, "请打开 www.Google.com/device 并且复制以下 8个 标识符进行授权设备登录...");

                            //Client.DoGoogleLogin().Wait();
                            Client.Login.DoLogin().Wait();
                            break;
                    }
                    //刷新背包
                    RefreshUserPackageGoods();
                    //丢弃多余物品
                    DoRecycleItems();
                    //炼化多余宠物
                    TransferAllPokemons();

                    while (IsRunning)
                    {
                        ExecuteFarmingPokestopsAndPokemons(client);
                        Thread.Sleep(3000);
                    }

                    #region 老版本
                    /*
            if (string.IsNullOrEmpty(Client.AccessToken))
            {
                ActionNow = "重试登录001";
                ColoredConsoleWrite(Colors.Cyan,
                    $"服务器未返回登录令牌.. 3秒后重新尝试登录...");
                throw new LoginAgainException();
            }

            ActionNow = "取信息";
            Client.SetServer().Wait();
            var profile = Client.GetProfile().Result;
            var settings = Client.GetSettings().Result;
            //var mapObjects = Client.GetMapObjects().Result;
            var inventory = Client.GetInventory().Result;
            var pokemons =
                inventory.InventoryDelta.InventoryItems.Select(i => i.InventoryItemData?.Pokemon)
                    .Where(p => p != null && p?.PokemonId > 0);

            RefreshUserInfo(profile.Profile.Username);

            // Write the players ingame details
            //ColoredConsoleWrite(Colors.Yellow, "----------------------------");
            //if (ClientSettings.AuthType == AuthType.Ptc)
            //{
            ColoredConsoleWrite(Colors.Cyan, $"帐号: {ClientSettings.PtcUsername} 登录成功!");
            //ColoredConsoleWrite(Colors.Cyan, "密码: " + ClientSettings.PtcPassword );
            //}
            //ColoredConsoleWrite(Colors.DarkGray, "昵称: " + profile.Profile.Username);
            //ColoredConsoleWrite(Colors.DarkGray, "阵营: " + profile.Profile.Team);
            //if (profile.Profile.Currency.ToArray()[0].Amount > 0) // If player has any pokecoins it will show how many they have.
            //    ColoredConsoleWrite(Colors.DarkGray, "金币: " + profile.Profile.Currency.ToArray()[0].Amount);
            //ColoredConsoleWrite(Colors.DarkGray, "星尘: " + profile.Profile.Currency.ToArray()[1].Amount + "\n");
            //ColoredConsoleWrite(Colors.DarkGray, "经度: " + ClientSettings.DefaultLatitude);
            //ColoredConsoleWrite(Colors.DarkGray, "纬度: " + ClientSettings.DefaultLongitude);
            //ColoredConsoleWrite(Colors.Yellow, "----------------------------");

            // I believe a switch is more efficient and easier to read.
            ExecuteTransferPokemons(pokemons);
            //进化全部
            if (ClientSettings.EvolveAllGivenPokemons)
                EvolveAllGivenPokemons(client, pokemons);
            //丢弃全部
            ActionNow = "丢弃物品";
            if (ClientSettings.Recycler)
                client.RecycleItems(client).Wait();

            Thread.Sleep(1000);
            //PrintLevel(client);



            ExecuteFarmingPokestopsAndPokemons(client);
            client.RecycleItems(client).Wait();
            Thread.Sleep(1000);
            */
                    #endregion
                }
                catch (TaskCanceledException) { ColoredConsoleWrite(Colors.Red, "任务取消异常 - 重启任务中... error:(1010)"); /*Execute(); */}
                catch (UriFormatException) { ColoredConsoleWrite(Colors.Red, "通讯地址错误 - 重启任务中... error:(1011)"); /*Execute();*/ }
                catch (ArgumentOutOfRangeException) { ColoredConsoleWrite(Colors.Red, "下标越界 - 重启任务中... error:(1012)"); /*Execute();*/ }
                catch (ArgumentNullException) { ColoredConsoleWrite(Colors.Red, "空参数错误 - 重启任务中... error:(1013)"); /*Execute(); */}
                catch (NullReferenceException) { ColoredConsoleWrite(Colors.Red, "空对象错误 - 重启任务中... error:(1014)"); /*Execute(); */}
                catch (PtcOfflineException)
                {
                    ColoredConsoleWrite(Colors.Red, "PTC服务器返回关闭状态! 10秒后重新尝试登录... error:(1015)");
                    Thread.Sleep(10000);
                }/*
                catch (MatchAgainException e)
                {
                    ColoredConsoleWrite(Colors.Red, e.ToString());
                }
                catch (JsonReaderException)
                {
                    ColoredConsoleWrite(Colors.Red, "登录失败! 5秒后重新尝试登录(通常是服务器太卡,没有正常返回登录信息)... error:(1016)");
                    Thread.Sleep(5000);
                }
                */
                catch (LoginAgainException)
                {
                    ColoredConsoleWrite(Colors.Red, "5秒后重新尝试登录(通常是服务器太卡,没有正常返回登录信息)... error:(1017)");
                    Thread.Sleep(5000);
                }
                catch (ThreadAbortException)
                {
                    ColoredConsoleWrite(Colors.Red, "任务执行结束... 1020");
                }
                catch (WebException)
                {
                    ColoredConsoleWrite(Colors.Red, "网络通讯失败! 5秒后重新尝试登录(请检查本地网络连接)... error:(1018)");
                    Thread.Sleep(5000);
                }
                catch (Exception ex) { ColoredConsoleWrite(Colors.Red, $" {ex.Message} \r\n {ex.StackTrace}"); /*Execute();*/ }
                finally
                {
                    if (cancel != null)
                        cancel.Cancel();
                    GC.Collect();
                    Thread.Sleep(1000);
                }
            }
            ColoredConsoleWrite(Colors.Yellow, "任务执行结束... 1000");
        }
        private string CallAPI(string elem, double lat, double lon)
        {
            using (XmlReader reader = XmlReader.Create(@"http://api.geonames.org/findNearby?lat=" + lat + "&lng=" + lon + "&username=demo"))
            {
                while (reader.Read())
                {
                    if (reader.IsStartElement())
                    {
                        switch (elem)
                        {
                            case "country":
                                if (reader.Name == "countryName")
                                {
                                    return reader.ReadString();
                                }
                                break;

                            case "place":
                                if (reader.Name == "toponymName")
                                {
                                    return reader.ReadString();
                                }
                                break;
                            default:
                                return "N/A";
                                break;
                        }
                    }
                }
            }
            return "Error";
        }
        /// <summary>
        /// 进化全部精灵
        /// </summary>
        /// <param name="client"></param>
        /// <param name="pokemonToEvolve"></param>
        /// <returns></returns>
        private void EvolveAllGivenPokemons(Client client, IEnumerable<PokemonData> pokemonToEvolve)
        {
            ActionNow = "进化精灵";
            /*
        foreach (var pokemon in pokemonToEvolve)
        {
            enum Holoholo.Rpc.Types.EvolvePokemonOutProto.Result {
                UNSET = 0;
                SUCCESS = 1;
                FAILED_POKEMON_MISSING = 2;
                FAILED_INSUFFICIENT_RESOURCES = 3;
                FAILED_POKEMON_CANNOT_EVOLVE = 4;
                FAILED_POKEMON_IS_DEPLOYED = 5;
            }
            }*/
            /*
            var countOfEvolvedUnits = 0;
            var xpCount = 0;

            EvolvePokemonOut evolvePokemonOutProto;
            do
            {
                evolvePokemonOutProto = client.EvolvePokemon(pokemon.Id).Result;
                //todo: someone check whether this still works

                if (evolvePokemonOutProto.Result == 1)
                {
                    ColoredConsoleWrite(Colors.Cyan,
                        $" {pokemon.PokemonId} 进化后 {evolvePokemonOutProto.ExpAwarded}xp");

                    countOfEvolvedUnits++;
                    xpCount += evolvePokemonOutProto.ExpAwarded;
                }
                else
                {
                    var result = evolvePokemonOutProto.Result;
                }
            } while (evolvePokemonOutProto.Result == 1);
            if (countOfEvolvedUnits > 0)
                ColoredConsoleWrite(Colors.Cyan,
                    $"Evolved {countOfEvolvedUnits} pieces of {pokemon.PokemonId} for {xpCount}xp");

            Thread.Sleep(3000);
        }
        */
        }
        /// <summary>
        /// 捕捉所有附近精灵
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        private void ExecuteCatchAllNearbyPokemons(Client client)
        {
            ActionNow = "捕捉精灵";

            //取当前坐标, 地图所有项
            var mapObjects = client.Map.GetMapObjects().Result;

            //取 所有 精灵
            var pokemons = mapObjects.Item1.MapCells
                                        .SelectMany(i => i.CatchablePokemons)
                                        .OrderBy(
                                                i =>
                                                    LocationUtils.CalculateDistanceInMeters(
                                                        new Location(Client.CurrentLatitude, Client.CurrentLongitude),
                                                        new Location(i.Latitude, i.Longitude))).ToList();

            foreach (var pokemon in pokemons)
            {
                if (!IsRunning) return;
                //更新坐标到 精灵旁
                var update = client.Player.UpdatePlayerLocation(pokemon.Latitude, pokemon.Longitude, 0).Result;
                //走路
                //WalkToLocation(pokemon.Latitude, pokemon.Longitude);

                var encounterPokemonResponse = client.Encounter.EncounterPokemon(pokemon.EncounterId, pokemon.SpawnPointId).Result;
                var pokemonStatus = encounterPokemonResponse.Status;
                if (pokemonStatus == EncounterResponse.Types.Status.PokemonInventoryFull)
                {
                    ColoredConsoleWrite(Colors.Red, "宠物背包已满...");
                    TransferAllPokemons(null);
                    continue;
                }
                if (pokemonStatus == EncounterResponse.Types.Status.EncounterPokemonFled)
                {
                    ColoredConsoleWrite(Colors.Red, $"精灵 {pokemon.PokemonId} ({encounterPokemonResponse?.WildPokemon?.PokemonData?.Cp} CP) 逃跑了..");
                    continue;
                }
                var pokemonCP = encounterPokemonResponse?.WildPokemon?.PokemonData?.Cp;
                CatchPokemonResponse caughtPokemonResponse;
                float? probability = encounterPokemonResponse.CaptureProbability?.CaptureProbability_[0];
                ItemId useBestBall = ItemId.ItemPokeBall;
                do
                {
                    useBestBall = GetBestBall(encounterPokemonResponse, probability.Value);

                    if (ClientSettings.RazzBerryMode == "cp")
                    {
                        if (pokemonCP > ClientSettings.RazzBerrySetting)
                            client.Encounter.UseCaptureItem(pokemon.EncounterId, ItemId.ItemRazzBerry, pokemon.SpawnPointId).Wait();
                    }
                    else if (ClientSettings.RazzBerryMode == "probability")
                    {
                        if (encounterPokemonResponse.CaptureProbability?.CaptureProbability_?.FirstOrDefault() < ClientSettings.RazzBerrySetting)//此处 空对象 CaptureProbability
                            client.Encounter.UseCaptureItem(pokemon.EncounterId, ItemId.ItemRazzBerry, pokemon.SpawnPointId).Wait();
                    }
                    caughtPokemonResponse = client.Encounter.CatchPokemon(pokemon.EncounterId, pokemon.SpawnPointId, useBestBall).Result;  //note: reverted from settings because this should not be part of settings but part of logic
                    PackageGoods.FirstOrDefault(f => f.ItemId == useBestBall).Count--;
                } while (caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchMissed || caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchEscape);

                string pokemonName = Convert.ToString(pokemon.PokemonId);
                //抓捕成功
                if (caughtPokemonResponse.Status == CatchPokemonResponse.Types.CatchStatus.CatchSuccess)
                {
                    ColoredConsoleWrite(Colors.Green, $"抓到精灵 {pokemonName} ({encounterPokemonResponse?.WildPokemon?.PokemonData?.Cp} CP, 完美度{ encounterPokemonResponse?.WildPokemon?.PokemonData.Perfect().Round(1)}% )");
                    foreach (int xp in caughtPokemonResponse.CaptureAward.Xp)
                        TotalExperience += xp;
                    TotalPokemon += 1;
                    //如果完美度小于设置, 并且 CP值小于设置, 则炼化 
                    if (encounterPokemonResponse?.WildPokemon?.PokemonData?.Perfect() <= ClientSettings.PerfectPokemonLimit &&
                        encounterPokemonResponse?.WildPokemon?.PokemonData?.Cp <= ClientSettings.TransferCPThreshold)
                    {
                        encounterPokemonResponse.WildPokemon.PokemonData.Id = caughtPokemonResponse.CapturedPokemonId;
                        TransferPokemonItem(encounterPokemonResponse?.WildPokemon?.PokemonData);
                    }
                }
                else
                    ColoredConsoleWrite(Colors.Red, $"精灵 {pokemonName} ({encounterPokemonResponse?.WildPokemon?.PokemonData?.Cp} CP) 逃跑了..");

                Thread.Sleep(3000);
            }

        }
        /// <summary>
        /// 操作附近所有的 补给站 和 精灵
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        private void ExecuteFarmingPokestopsAndPokemons(Client client)
        {
            int count = 0;
            LABEL_GET_MAP_OBJECT:
            //获取地图所有 项目
            var mapObjects = client.Map.GetMapObjects().Result;
            //获取所有补给站
            var pokeStops = mapObjects.Item1.MapCells
                                        .SelectMany(i => i.Forts)
                                        .Where(i =>
                                                    i.Type == FortType.Checkpoint &&
                                                    i.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime()
                                          ).OrderBy(o =>
                                                    LocationUtils.CalculateDistanceInMeters(new Location(client.CurrentLatitude, client.CurrentLongitude), new Location(o.Latitude, o.Longitude))
                                          ).ToList();

            if (pokeStops.Count() == 0)
            {
                if (count < 3)
                {
                    ColoredConsoleWrite(Colors.BlueViolet, $"附近无补给站和精灵({++count})?(通常是服务器宿机中,暂时未刷新项目)");
                    Thread.Sleep(3000);
                    goto LABEL_GET_MAP_OBJECT;
                }
                throw new LoginAgainException();
            }
            foreach (var pokeStop in pokeStops)
            {
                if (!IsRunning) return;
                //ColoredConsoleWrite(Colors.Yellow, $"当前坐标: { Math.Round(client._currentLat, 5)}: { Math.Round(client._currentLng, 5)} 补给坐标: {Math.Round(pokeStop.Latitude, 5)}: {Math.Round(pokeStop.Longitude, 5)}");
                //更新 坐标到 补给站旁边
                //var update = client.UpdatePlayerLocation(pokeStop.Latitude, pokeStop.Longitude);
                WalkToLocation(pokeStop.Latitude, pokeStop.Longitude);

                ActionNow = "补给";

                //取 补给站信息

                var fortInfo = client.Fort.GetFort(pokeStop.Id, pokeStop.Latitude, pokeStop.Longitude).Result;
                //转动 补给站
                var fortSearch = client.Fort.SearchFort(pokeStop.Id, pokeStop.Latitude, pokeStop.Longitude).Result;

                StringWriter PokeStopOutput = new StringWriter();
                PokeStopOutput.Write($"");
                if (fortInfo.Name != string.Empty)
                    PokeStopOutput.Write("补给站: " + fortInfo.Name);
                if (fortSearch.ExperienceAwarded != 0)
                    PokeStopOutput.Write($", 经验: {fortSearch.ExperienceAwarded}");
                if (fortSearch.GemsAwarded != 0)
                    PokeStopOutput.Write($", 宝物: {fortSearch.GemsAwarded}");
                if (fortSearch.PokemonDataEgg != null)
                {
                    PokeStopOutput.Write($", 精灵蛋: {fortSearch.PokemonDataEgg}");
                    PackageGoods.FirstOrDefault(f => f.ItemId == ItemId.ItemLuckyEgg).Count++;
                }
                if (fortSearch.ItemsAwarded != null)
                {
                    PokeStopOutput.Write($", 其它: {fortSearch.ItemsAwarded.ToItemsString()} ");
                    AddPackageGoodsItems(fortSearch.ItemsAwarded);
                }

                if (fortSearch.Result == FortSearchResponse.Types.Result.InventoryFull)
                {
                    DoRecycleItems();
                    //client.Inventory.RecycleItems(client).Wait();
                }
                else if (fortSearch.Result != FortSearchResponse.Types.Result.Success)
                {
                    ColoredConsoleWrite(Colors.Red, $"fortSearch Status: {fortSearch.Result} ");
                    PokeStopOutput.Write($", Status: {fortSearch.Result} ");
                }
                ColoredConsoleWrite(Colors.Cyan, PokeStopOutput.ToString());

                if (fortSearch.ExperienceAwarded != 0)
                    TotalExperience += (fortSearch.ExperienceAwarded);

                Thread.Sleep(1000);

                ExecuteCatchAllNearbyPokemons(client);

                RefreshUserInfo(ClientSettings.PtcUsername);

                TransferAllPokemons();
            }
        }
        /// <summary>
        /// 模拟走路到达指定坐标点
        /// </summary>
        /// <param name="lat"></param>
        /// <param name="lng"></param>
        /// <returns></returns>
        private void WalkToLocation(double lat, double lng)
        {
            ActionNow = "行走";
            // 10km/h
            //double walkSpeed = 20;

            double speedInMetersPerSecond = ClientSettings.WalkSpeed / 3.6;
            Location targetLocation = new Location(lat, lng);
            Location sourceLocation = new Location(client.CurrentLatitude, client.CurrentLongitude);
            double nextWaypointBearing = LocationUtils.DegreeBearing(sourceLocation, targetLocation);
            double nextWaypointDistance = speedInMetersPerSecond;
            Location waypoint = LocationUtils.CreateWaypoint(sourceLocation, nextWaypointDistance, nextWaypointBearing);

            DateTime requestSendDateTime = DateTime.Now;
            var result = client.Player.UpdatePlayerLocation(waypoint.Latitude, waypoint.Longitude, client.CurrentAltitude).Result;
            double hasKid = Math.Round(LocationUtils.CalculateDistanceInMeters(sourceLocation, targetLocation), 0);
            ColoredConsoleWrite(Colors.DimGray, $"模拟以({ClientSettings.WalkSpeed}km/h时速)前往下一个地点({hasKid})米. 耗时约{ Math.Round(hasKid / speedInMetersPerSecond, 0)}秒");
            Thread.Sleep((int)(hasKid / speedInMetersPerSecond) * 1000);
            do
            {

                speedInMetersPerSecond = new Random().Next((int)ClientSettings.WalkSpeed - 5, (int)ClientSettings.WalkSpeed + 5) / 3.6;
                Thread.Sleep(1000);
                double millisecondsUntilGetUpdatePlayerLocationResponse = (DateTime.Now - requestSendDateTime).TotalMilliseconds;

                //sourceLocation = new Location(client._currentLat, client._currentLng);
                sourceLocation.Latitude = client.CurrentLatitude;
                sourceLocation.Longitude = client.CurrentLongitude;

                ClientSettings.CurrentLatitude = Math.Round(client.CurrentLatitude, 6);
                ClientSettings.CurrentLongitude = Math.Round(client.CurrentLongitude, 6);

                nextWaypointDistance = millisecondsUntilGetUpdatePlayerLocationResponse / 1000 * speedInMetersPerSecond;
                nextWaypointBearing = LocationUtils.DegreeBearing(sourceLocation, targetLocation);
                waypoint = LocationUtils.CreateWaypoint(sourceLocation, nextWaypointDistance, nextWaypointBearing);

                requestSendDateTime = DateTime.Now;
                result = client.Player.UpdatePlayerLocation(waypoint.Latitude, waypoint.Longitude, 0).Result;

                hasKid = Math.Round(LocationUtils.CalculateDistanceInMeters(sourceLocation, targetLocation), 0);
            } while (hasKid >= 30);
            client.Player.UpdatePlayerLocation(lat, lng, client.CurrentAltitude).Wait();
            ColoredConsoleWrite(Colors.Gray, $"已到达指定坐标点:{client.CurrentLatitude} {client.CurrentLongitude}");

        }

        private void DoRecycleItems()
        {
            //var items = client.Inventory.GetInventory().Result.InventoryDelta.InventoryItems.Select(s => s.InventoryItemData?.Item).Where(w => w != null).ToList();
            var filter_items = PackageGoods
                                        .Where(w => ClientSettings.ItemRecycleFilter.Any(a => a.Key == w.ItemId && w.Count > a.Value));
            RecycleInventoryItemResponse result;
            foreach (var item in filter_items)
            {
                result = client.Inventory.RecycleItem(item.ItemId, item.Count).Result;
                if (result.Result == RecycleInventoryItemResponse.Types.Result.Success)
                {
                    ColoredConsoleWrite(Colors.Blue, $"成功丢弃. {item.ItemId}  数量:{item.Count}");
                    item.Count -= item.Count;
                }
                Thread.Sleep(1000);
            }

        }
        //private string GetFriendlyItemsString(IEnumerable<FortSearchResponse.Types.ItemAward> items)
        //{
        //    var enumerable = items as IList<FortSearchResponse.Types.ItemAward> ?? items.ToList();

        //    if (!enumerable.Any())
        //        return string.Empty;

        //    return enumerable.GroupBy(i => i.ItemId)
        //            .Select(kvp => new { ItemName = kvp.Key.ToString().Substring(4), Amount = kvp.Sum(x => x.ItemCount) })
        //            .Select(y => $"{y.Amount}x {y.ItemName}")
        //            .Aggregate((a, b) => $"{a}, {b}");
        //}

        #region 炼化精灵
        //====================炼化精灵======================
        /// <summary>
        /// 炼化所有 精灵
        /// </summary>
        /// <param name="client"></param>
        /// <param name="unwantedPokemons"></param>
        /// <param name="keepPerfectPokemonLimit"></param>
        /// <returns></returns>
        private void TransferAllPokemons(List<PokemonData> unwantedPokemons = null)
        {
            if (unwantedPokemons == null)
            {
                unwantedPokemons = client.Inventory.GetInventory().Result.InventoryDelta.InventoryItems
                                    .Select(i => i.InventoryItemData?.PokemonData)
                                    .Where(p => p != null && p?.PokemonId > 0).ToList();
            }

            var willTransferPokemons = unwantedPokemons
                                                            .Where(w =>
                                                                    w.Perfect() <= ClientSettings.PerfectPokemonLimit &&
                                                                    w.Cp <= ClientSettings.TransferCPThreshold
                                                             ).ToList();

            foreach (var pokemon in willTransferPokemons)
            {
                if (pokemon.Favorite == 0)
                {
                    //炼化指定精灵
                    TransferPokemonItem(pokemon);

                    Thread.Sleep(1000);
                }
            }
        }
        /// <summary>
        /// 炼化 指定精灵
        /// </summary>
        /// <param name="willTransferPokemonItem"></param>
        private void TransferPokemonItem(PokemonData willTransferPokemonItem)
        {
            var transferPokemonResponse = client.Inventory.TransferPokemon(willTransferPokemonItem.Id).Result;

            /*
            ReleasePokemonOutProto.Status {
                UNSET = 0;
                SUCCESS = 1;
                POKEMON_DEPLOYED = 2;
                FAILED = 3;
                ERROR_POKEMON_IS_EGG = 4;
            }*/
            string pokemonName = Convert.ToString(willTransferPokemonItem.PokemonId);
            if (transferPokemonResponse.Result == ReleasePokemonResponse.Types.Result.Success)
            {
                ColoredConsoleWrite(Colors.Magenta, $"成功炼化 {pokemonName} ({willTransferPokemonItem.Cp} CP) 完美度{willTransferPokemonItem.Perfect().Round(1)}%. 小于 {ClientSettings.PerfectPokemonLimit}%");
            }
            else
            {
                var status = transferPokemonResponse.Result;

                ColoredConsoleWrite(Colors.Red, $"未知原因无法炼化小精灵 {pokemonName} ({willTransferPokemonItem.Cp} CP). " +
                                         $"状态为 {status}");
            }
        }

        private void TransferDuplicatePokemon(Client client)
        {

            //ColoredConsoleWrite(ConsoleColors.White, $"Check for duplicates");
            /*
            var inventory = client.GetInventory().Result;
            var allpokemons =
                inventory.InventoryDelta.InventoryItems.Select(i => i.InventoryItemData?.Pokemon)
                    .Where(p => p != null && p?.PokemonId > 0);

            var dupes = allpokemons.OrderBy(x => x.Cp).Select((x, i) => new { index = i, value = x })
                .GroupBy(x => x.value.PokemonId)
                .Where(x => x.Skip(1).Any());

            for (var i = 0; i < dupes.Count(); i++)
            {
                for (var j = 0; j < dupes.ElementAt(i).Count() - 1; j++)
                {
                    var dubpokemon = dupes.ElementAt(i).ElementAt(j).value;
                    if (dubpokemon.Favorite == 0)
                    {
                        var transfer = client.TransferPokemon(dubpokemon.Id).Result;
                        string pokemonName;
                        if (ClientSettings.Language == "german")
                        {
                            string name_english = Convert.ToString(dubpokemon.PokemonId);
                            var request = (HttpWebRequest)WebRequest.Create("http://boosting-service.de/pokemon/index.php?pokeName=" + name_english);
                            var response = (HttpWebResponse)request.GetResponse();
                            pokemonName = new StreamReader(response.GetResponseStream()).ReadToEnd();
                        }
                        else
                            pokemonName = Convert.ToString(dubpokemon.PokemonId);
                        ColoredConsoleWrite(Colors.DarkGreen,
                            $"已炼化小精灵 {pokemonName} ({dubpokemon.Cp} CP) (最高 {dupes.ElementAt(i).Last().value.Cp})");

                    }
                }
            }
            */
        }

        private void TransferAllWeakPokemon(Client client, int cpThreshold)
        {
            //ColoredConsoleWrite(ConsoleColors.White, $"Firing up the meat grinder");

            PokemonId[] doNotTransfer = new PokemonId[0] //these will not be transferred even when below the CP threshold
            { // DO NOT EMPTY THIS ARRAY
                //PokemonId.Pidgey,
                //PokemonId.Rattata,
                //PokemonId.Weedle,
                //PokemonId.Zubat,
                //PokemonId.Caterpie,
                //PokemonId.Pidgeotto,
                //PokemonId.NidoranFemale,
                //PokemonId.Paras,
                //PokemonId.Venonat,
                //PokemonId.Psyduck,
                //PokemonId.Poliwag,
                //PokemonId.Slowpoke,
                //PokemonId.Drowzee,
                //PokemonId.Gastly,
                //PokemonId.Goldeen,
                //PokemonId.Staryu,
                //PokemonId.Magikarp,
                //PokemonId.Eevee//,
                //PokemonId.Dratini
            };

            var inventory = client.Inventory.GetInventory().Result;
            var pokemons = inventory.InventoryDelta.InventoryItems
                                .Select(i => i.InventoryItemData?.PokemonData)
                                .Where(p => p != null && p?.PokemonId > 0)
                                .ToArray();

            //foreach (var unwantedPokemonType in unwantedPokemonTypes)
            {
                List<PokemonData> pokemonToDiscard;
                if (doNotTransfer.Count() != 0)
                    pokemonToDiscard = pokemons.Where(p => !doNotTransfer.Contains(p.PokemonId) && p.Cp < cpThreshold).OrderByDescending(p => p.Cp).ToList();
                else
                    pokemonToDiscard = pokemons.Where(p => p.Cp < cpThreshold).OrderByDescending(p => p.Cp).ToList();


                //var unwantedPokemon = pokemonOfDesiredType.Skip(1) // keep the strongest one for potential battle-evolving
                //                                          .ToList();
                //ColoredConsoleWrite(Colors.Gray, $"开始批量炼化所有小于 {cpThreshold} CP 的小精灵(共{pokemonToDiscard.Count}个) .");
                TransferAllPokemons(pokemonToDiscard);

            }

            //ColoredConsoleWrite(Colors.Gray, $"所有转换完毕...");
        }

        //=================================================
        #endregion

        public void PrintLevel(Client client)
        {

            var inventory = client.Inventory.GetInventory().Result;
            var stats = inventory.InventoryDelta.InventoryItems.Select(i => i.InventoryItemData?.PlayerStats).ToArray();
            foreach (var v in stats)
                if (v != null)
                {
                    int XpDiff = GetXpDiff(client, v.Level);
                    if (ClientSettings.LevelOutput == "time")
                        ColoredConsoleWrite(Colors.Yellow, $"当前等级: " + v.Level + " (" + (v.Experience - v.PrevLevelXp - XpDiff) + "/" + (v.NextLevelXp - v.PrevLevelXp - XpDiff) + ")");
                    else if (ClientSettings.LevelOutput == "levelup")
                        if (Currentlevel != v.Level)
                        {
                            Currentlevel = v.Level;
                            ColoredConsoleWrite(Colors.Magenta, $"当前等级: " + v.Level + ". 升级下一级需要: " + (v.NextLevelXp - v.Experience));
                        }
                }
            if (ClientSettings.LevelOutput == "levelup")
                Thread.Sleep(1000);
            else
                Thread.Sleep(ClientSettings.LevelTimeInterval * 1000);

        }

        // Pulled from NecronomiconCoding
        public string _getSessionRuntimeInTimeFormat()
        {
            return (DateTime.Now - InitSessionDateTime).ToString(@"dd\.hh\:mm\:ss");
        }

        public void RefreshUserInfo(string Username)
        {

            ActionNow = "刷新个人信息";
            GetInventoryResponse inventory;
            PlayerStats[] stats;
            GetPlayerResponse profile;
            inventory = client.Inventory.GetInventory().Result;
            stats = inventory.InventoryDelta.InventoryItems.Select(i => i.InventoryItemData?.PlayerStats).ToArray();
            profile = client.Player.GetPlayer().Result;
            foreach (var v in stats)
                if (v != null)
                {
                    int XpDiff = GetXpDiff(client, v.Level);
                    NickName = Username;
                    Level = v.Level;
                    Runtime = _getSessionRuntimeInTimeFormat();
                    XPNow = (v.Experience - v.PrevLevelXp - XpDiff);
                    NextLevelXP = (v.NextLevelXp - v.PrevLevelXp - XpDiff);
                    Stardust = profile.PlayerData.Currencies.ToArray()[1].Amount;
                    XPOfHour = Math.Round(TotalExperience / GetRuntime());
                    PokemonOfHour = Math.Round(TotalPokemon / GetRuntime());
                    //ColoredConsoleWrite(Colors.Beige, string.Format(Username + " | Level: {0:0} - ({2:0} / {3:0}) | Runtime {1} | Stardust: {4:0}",
                    //    v.Level,
                    //    _getSessionRuntimeInTimeFormat(),
                    //    (v.Experience - v.PrevLevelXp - XpDiff),
                    //    (v.NextLevelXp - v.PrevLevelXp - XpDiff),
                    //    profile.Profile.Currency.ToArray()[1].Amount)
                    //    + " | XP/Hour: " + Math.Round(TotalExperience / GetRuntime()) + " | Pokemon/Hour: " + Math.Round(TotalPokemon / GetRuntime()));
                }

        }

        /// <summary>
        /// 刷新用户背包
        /// </summary>
        public void RefreshUserPackageGoods()
        {
            lock (PackageGoods)
            {
                var items = client.Inventory.GetInventory().Result.InventoryDelta.InventoryItems
                                    .Where(w => w.InventoryItemData != null && w.InventoryItemData.Item != null)
                                    .Select(s => new ItemPackGood { ItemId = s.InventoryItemData.Item.ItemId, Count = s.InventoryItemData.Item.Count });
                PackageGoods = new ObservableCollection<ItemPackGood>(items);
            }
        }
        public void AddPackageGoodsItems(IList<ItemAward> itemAwards)
        {
            ItemPackGood good = null;
            foreach (var item in itemAwards)
            {
                good = PackageGoods.FirstOrDefault(f => f.ItemId == item.ItemId);
                if (good != null)
                {
                    good.Count += item.ItemCount;
                }
                else
                {
                    PackageGoods.Add(new ItemPackGood { ItemId = item.ItemId, Count = item.ItemCount });
                }
                good = null;
            }
        }

        public ItemId GetBestBall(EncounterResponse encounter, float probability)
        {
            var pokemonCp = encounter.WildPokemon?.PokemonData.Cp;
            var pokemonId = encounter.WildPokemon?.PokemonData.PokemonId;
            var iV = encounter.WildPokemon?.PokemonData.Perfect();

            var pokeBallsCount = PackageGoods.FirstOrDefault(f => f.ItemId == ItemId.ItemPokeBall)?.Count ?? 0;
            var greatBallsCount = PackageGoods.FirstOrDefault(f => f.ItemId == ItemId.ItemGreatBall)?.Count ?? 0;
            var ultraBallsCount = PackageGoods.FirstOrDefault(f => f.ItemId == ItemId.ItemUltraBall)?.Count ?? 0;
            var masterBallsCount = PackageGoods.FirstOrDefault(f => f.ItemId == ItemId.ItemMasterBall)?.Count ?? 0;

            if (masterBallsCount > 0 && (pokemonCp >= 1500 || probability < 0.05))
                return ItemId.ItemMasterBall;

            if (ultraBallsCount > 0 && (pokemonCp >= 1250 ||
                    iV >= 85 ||
                    probability < 0.1))
                return ItemId.ItemUltraBall;

            if (greatBallsCount > 0 && (
                    pokemonCp >= 1000 ||
                    iV >= 95 ||
                    probability < 0.2))
                return ItemId.ItemGreatBall;

            if (pokeBallsCount > 0)
                return ItemId.ItemPokeBall;
            if (greatBallsCount > 0)
                return ItemId.ItemGreatBall;
            if (ultraBallsCount > 0)
                return ItemId.ItemUltraBall;
            if (masterBallsCount > 0)
                return ItemId.ItemMasterBall;

            return ItemId.ItemUnknown;
        }
        public static int GetXpDiff(Client client, int Level)
        {
            switch (Level)
            {
                case 1:
                    return 0;
                case 2:
                    return 1000;
                case 3:
                    return 2000;
                case 4:
                    return 3000;
                case 5:
                    return 4000;
                case 6:
                    return 5000;
                case 7:
                    return 6000;
                case 8:
                    return 7000;
                case 9:
                    return 8000;
                case 10:
                    return 9000;
                case 11:
                    return 10000;
                case 12:
                    return 10000;
                case 13:
                    return 10000;
                case 14:
                    return 10000;
                case 15:
                    return 15000;
                case 16:
                    return 20000;
                case 17:
                    return 20000;
                case 18:
                    return 20000;
                case 19:
                    return 25000;
                case 20:
                    return 25000;
                case 21:
                    return 50000;
                case 22:
                    return 75000;
                case 23:
                    return 100000;
                case 24:
                    return 125000;
                case 25:
                    return 150000;
                case 26:
                    return 190000;
                case 27:
                    return 200000;
                case 28:
                    return 250000;
                case 29:
                    return 300000;
                case 30:
                    return 350000;
                case 31:
                    return 500000;
                case 32:
                    return 500000;
                case 33:
                    return 750000;
                case 34:
                    return 1000000;
                case 35:
                    return 1250000;
                case 36:
                    return 1500000;
                case 37:
                    return 2000000;
                case 38:
                    return 2500000;
                case 39:
                    return 1000000;
                case 40:
                    return 1000000;
            }
            return 0;
        }

        public async Task UseLuckyEgg()
        {


            if (client != null)
            {
                try
                {
                    var luckEggItems = PackageGoods.FirstOrDefault(c => c.ItemId == ItemId.ItemLuckyEgg);
                    if (luckEggItems.Count > 0)
                    {
                        var useItemXpBoostRequest = client.Inventory.UseItemXpBoost();
                        if (useItemXpBoostRequest.Result.Result == UseItemXpBoostResponse.Types.Result.Success)
                        {
                            ColoredConsoleWrite(Colors.Green, $"成功使用一个幸运蛋. 还剩下 {--luckEggItems.Count} 个幸运蛋.");
                            ColoredConsoleWrite(Colors.Yellow, $"幸运蛋有效期至: {DateTime.Now.AddMinutes(30).ToString()}");
                        }
                        else
                        {
                            ColoredConsoleWrite(Colors.Red, $"使用幸运蛋失败...");
                        }
                    }
                    else
                    {
                        ColoredConsoleWrite(Colors.Red, $"没有足够的幸运蛋可以使用.");
                    }
                }
                catch (Exception ex)
                {
                    ColoredConsoleWrite(Colors.Red, $"使用幸运蛋出错: {ex}");
                }
            }
            else
            {
                ColoredConsoleWrite(Colors.Red, "请在开始前使用幸运蛋.");
            }

        }

        public double GetRuntime()
        {
            return ((DateTime.Now - TimeStarted).TotalSeconds) / 3600;
        }

    }

    public partial class PokeClient
    {

        private string _runTime = string.Empty;
        public string Runtime
        {
            get { return _runTime; }
            set { Set(() => Runtime, ref _runTime, value); }
        }


        private int _level = 1;
        public int Level
        {
            get { return _level; }
            set { Set(() => Level, ref _level, value); }
        }

        private string _nickName = string.Empty;
        public string NickName
        {
            get { return _nickName; }
            set { Set(() => NickName, ref _nickName, value); }
        }

        private int _stardust = 0;
        public int Stardust
        {
            get { return _stardust; }
            set { Set(() => Stardust, ref _stardust, value); }
        }

        private long _xpNow = 0;
        public long XPNow
        {
            get { return _xpNow; }
            set { Set(() => XPNow, ref _xpNow, value); }
        }

        private long _nextLevelXP = 0;
        public long NextLevelXP
        {
            get { return _nextLevelXP; }
            set { Set(() => NextLevelXP, ref _nextLevelXP, value); }
        }

        private double _xpOfHour = 0;
        public double XPOfHour
        {
            get { return _xpOfHour; }
            set { Set(() => XPOfHour, ref _xpOfHour, value); }
        }

        private double _pokemonOfHour = 0;
        public double PokemonOfHour
        {
            get { return _pokemonOfHour; }
            set { Set(() => PokemonOfHour, ref _pokemonOfHour, value); }
        }


        private bool _isRunning = false;
        public bool IsRunning
        {
            get { return _isRunning; }
            set { Set(() => IsRunning, ref _isRunning, value); }
        }

        private bool _isSelect = false;
        public bool IsSelect
        {
            get { return _isSelect; }
            set { Set(() => IsSelect, ref _isSelect, value); }
        }

        internal DateTime lastActionTime = DateTime.Now;
        private string _actionNow = string.Empty;
        public string ActionNow
        {
            get { return _actionNow; }
            set
            {
                lastActionTime = DateTime.Now;
                Set(() => ActionNow, ref _actionNow, value);
            }
        }

        /// <summary>
        /// 物品背包
        /// </summary>
        public ObservableCollection<ItemPackGood> PackageGoods { get { return _packageGoods; } set { Set(ref _packageGoods, value); } }
        private ObservableCollection<ItemPackGood> _packageGoods = new ObservableCollection<ItemPackGood>();

        /// <summary>
        /// 宠物背包
        /// </summary>
        public ObservableCollection<ItemData> PackagePokemons { get { return _packagePokemons; } set { Set(ref _packagePokemons, value); } }
        private ObservableCollection<ItemData> _packagePokemons = new ObservableCollection<ItemData>();
        //private bool _isThingFilterOpen = true;
        //public bool IsThingFilterOpen
        //{
        //    get { return _isThingFilterOpen; }
        //    set { Set(() => IsThingFilterOpen, ref _isThingFilterOpen, value); }
        //}
    }


    public static partial class PokeService
    {
        public static void Start(PokeClient pokeClient)
        {

        }

        public static void Stop(PokeClient pokeClient)
        {

        }

        public static bool Login(PokeClient pokeClient)
        {
            return false;
        }

        public static bool InitServer(PokeClient pokeClient)
        {
            return false;
        }
        public static void GetMapObjects(PokeClient pokeClient)
        {

        }

        public static void GetInventory(PokeClient pokeClient)
        {

        }
    }
    public class AbortTaskException : Exception
    {

    }

}
